/******************************************************************************/
/* File: ipaddress.cpp                                                        */
/******************************************************************************/
/* This source code file is the source file for the IPAddress class - this    */
/* class is used to provide transparent IPv4 and IPv6 address support for the */
/* Socket classes within the library.                                         */
/******************************************************************************/

/******************************************************************************/
/* Include class header file.                                                 */
/******************************************************************************/
#include "socketClass.h"

/******************************************************************************/
/* class IPAddress.                                                           */
/******************************************************************************/
/* IPAddress Static Member Variables.                                         */
/*                                                                            */
/* mutexKernelLock : Locks access to the gethostbyname2() and gethostbyaddr() */
/*                   function calls.  These functions and the results that    */
/*                   they return are non-reentrant.  Code that calls these    */
/*                   functions and then processes their return values forms a */
/*                   critical section.  This MutualExclusion ensures that     */
/*                   only one thread can run this code at any time.           */
/******************************************************************************/
pthread_mutex_t IPAddress::mutexKernelLock = PTHREAD_MUTEX_INITIALIZER;

/******************************************************************************/
/* IPAddress Constructor.                                                     */
/*                                                                            */
/* The default constructor assigns the localhost IPv4 address to the class.   */
/* The Address type and length are set, memory is allocated for the host name */
/* string.  Finally the address itself is filled with the correct values and  */
/* the string representation of the address is filled.                        */
/******************************************************************************/
IPAddress::IPAddress() {
    try {
        iAddressType = AF_INET;
        
        pcHostName = new char[strlen("localhost") + 1];
        strcpy(pcHostName, "localhost");
        
        // Initialize the ip address as '127.0.0.1' but saved through numeric array.
        iAddressLength = 4;
        pcAddress[0] = 127;
        pcAddress[1] = 0;
        pcAddress[2] = 0;
        pcAddress[3] = 1;

        strcpy(pcStrAddress, "127.0.0.1");
    }
    catch (...) {
        throw SocketException(SocketException::errMemory);
    }
}

/******************************************************************************/
/* IPAddress Destructor.                                                      */
/*                                                                            */
/* The base class destructor deletes any memory allocated for pointers within */
/* the class.                                                                 */
/******************************************************************************/
IPAddress::~IPAddress() {
    delete pcHostName;
}

/******************************************************************************/
/* Private methods of IPAddress.                                              */
/******************************************************************************/
/* void privSetHostName(const char *pcNewHostName)                            */
/*                                                                            */
/* This private method is used to set the contents of the pcHostName member   */
/* variable.  Previous memory allocated to this variable is deleted.  Memory  */
/* is then reallocated as required to hold the new string which is then       */
/* copied to the new memory location.                                         */
/******************************************************************************/
void IPAddress::privSetHostName(const char *pcNewHostName) {
    try {
        char *pcTemp = new char[strlen(pcNewHostName) + 1];
        delete pcHostName;
        pcHostName = pcTemp;
        strcpy(pcHostName, pcNewHostName);
    }
    catch (...) {
        throw SocketException(SocketException::errMemory);
    }
}

/******************************************************************************/
/* void privSetAddress(const char *pcNewAddress, const int iNewAddressLength) */
/*                                                                            */
/* This private method is used to set the contents of the iAddressLength and  */
/* pcAddress member variables.  Address Length bytes are copied from the      */
/* source address to the pcAddress array.  This array is 16 bytes long, long  */
/* enough to hold both IPv4 and IPv6 addresses.                               */
/******************************************************************************/
void IPAddress::privSetAddress(const char *pcNewAddress, const int iNewAddressLength) {
    iAddressLength = iNewAddressLength;
    // pcNewAddress and pcAddress are both char arrays.
    memcpy(pcAddress, pcNewAddress, iAddressLength);
}

/******************************************************************************/
/* Assignment Operator Overloads.                                             */
/******************************************************************************/
/* Assign equal to existing IPAddress class.                                  */
/*                                                                            */
/* Allows us to assign equality to an existing IPAddress class.  We copy over */
/* values of the variables iAddressType and pcStrAddress stored within the    */
/* original class.  We use the protected methods privSetHostName() and        */
/* privSetAddress() to set the other internal member variables.               */
/******************************************************************************/
IPAddress & IPAddress::operator = (const IPAddress &cOrigAddr) {
    iAddressType = cOrigAddr.iAddressType;
    privSetHostName(cOrigAddr.pcHostName);
    privSetAddress(cOrigAddr.pcAddress, cOrigAddr.iAddressLength);
    strcpy(pcStrAddress, cOrigAddr.pcStrAddress);
    return *this;
}

/******************************************************************************/
/* Assign equal to host name.                                                 */
/*                                                                            */
/* Allows us to assign equality to an existing string host name.  The         */
/* SetHostName() method is called to set the IPv4 address for the provided    */
/* Host-Name.  The previous value is unchanged if SetHostName() fails.        */
/******************************************************************************/
IPAddress & IPAddress::operator = (const char *pcHostName) {
    SetHostName(pcHostName, false);
    return *this;
}

/******************************************************************************/
/* Assign equal to IPv4 Address Structure.                                    */
/*                                                                            */
/* Allows as to assign equality to an existing IPv4 in_addr structure.  The   */
/* SetAddress() method is called with a pointer to the structure cast as a    */
/* char pointer to complete the conversion.                                   */
/******************************************************************************/
IPAddress & IPAddress::operator = (const struct in_addr sIP4Addr) {
    SetAddress((char *)&sIP4Addr, false);
    return *this;
}

/******************************************************************************/
/* Assign equal to IPv6 Address Structure.                                    */
/*                                                                            */
/* Allows as to assign equality to an existing IPv6 in6_addr structure.  The  */
/* SetAddress() method is called with a pointer to the structure cast as a    */
/* char pointer to complete the conversion.                                   */
/******************************************************************************/
IPAddress & IPAddress::operator = (const struct in6_addr sIP6Addr) {
    SetAddress((char *)&sIP6Addr, true);
    return *this;
}

/******************************************************************************/
/* Equality Operator Overloads.                                               */
/******************************************************************************/
/* Check equality with an existing IPAddress class.                           */
/*                                                                            */
/* Allows us to check equality with another IPAddress class.  Equality exists */
/* if the both address types and lengths are equal and the contents of the    */
/* pcAddress arrays are equal up to the correct length.  We return any        */
/* positive results on all three of these tests.                              */
/******************************************************************************/
bool IPAddress::operator == (const IPAddress &cOtherAddr) const {
    return ((iAddressType == cOtherAddr.iAddressType) &&
            (iAddressLength == cOtherAddr.iAddressLength) &&
            (bcmp(pcAddress, cOtherAddr.pcAddress, iAddressLength) == 0));
}

/******************************************************************************/
/* Check equality with a host name.                                           */
/*                                                                            */
/* Allows us to check equality with an existing string hostname.  A temporary */
/* IPAddress class is created with the given hostname and the same type as    */
/* the existing IPAddress class.  The two class instances are then compared   */
/* and the result is returned.                                                */
/******************************************************************************/
bool IPAddress::operator == (const char *pcHostName) const {
    IPAddress cCheckAddr;

    cCheckAddr.SetHostName(pcHostName, (iAddressType == AF_INET6));
    return ((*this) == cCheckAddr);
}

/******************************************************************************/
/* Check equality with an IPv4 Address Structure.                             */
/*                                                                            */
/* Allows as to check equality to an existing IPv4 in_addr structure.  A      */
/* temporary IPAddress class is created with the given IPv4 address.  This is */
/* then compared with the existing IPAddress class and the result is returned.*/
/******************************************************************************/
bool IPAddress::operator == (const struct in_addr sIP4Addr) const {
    IPAddress cCheckAddr;

    cCheckAddr = sIP4Addr;
    return ((*this) == cCheckAddr);
}

/******************************************************************************/
/* Check equality with an IPv6 Address Structure.                             */
/*                                                                            */
/* Allows as to check equality to an existing IPv6 in6_addr structure.  A     */
/* temporary IPAddress class is created with the given IPv6 address.  This is */
/* then compared with the existing IPAddress class and the result is returned.*/
/******************************************************************************/
bool IPAddress::operator == (const struct in6_addr sIP6Addr) const {
    IPAddress cCheckAddr;

    cCheckAddr = sIP6Addr;
    return ((*this) == cCheckAddr);
}

/******************************************************************************/
/* bool SetHostName(const char *pcNewHostName, bool bIPv6)                    */
/*                                                                            */
/* This method is called to set the IPAddress class contents to the address   */
/* specified by the provided hostname.  An extra parameter is used to allow   */
/* specification of an IPv4 or IPv6 address.  Since the gethostbyname2()      */
/* function call is not re-entrant, this function call and all operations on  */
/* the return value of the function call form a critical section that must be */
/* protected by a Mutual Exclusion.  The MutEx is locked at the start of the  */
/* method and unlocked before returning.  If gethostbyname2() fails, then we  */
/* cannot obtain the IP address of the host name, no internal variables are   */
/* set - (the class contains the old address still), and false is returned.   */
/* If the call succeeds, internal variables are set based on the results, the */
/* address type and length, the first address of the list that is returned is */
/* stored in the pcAddress field, as well as string representations of the    */
/* host name and ip address.  These values are stored in protected variables  */
/* so they can be re-used.                                                    */
/******************************************************************************/
void IPAddress::SetHostName(const char *pcNewHostName, bool bIPv6) {
    
    /*******************************************************************
       struct hostent {  
           char  *h_name;       // host name  
           char **h_aliases;    // host aliases name 
           int    h_addrtype;   // address family: AF_INET / AF_INET6  
           int    h_lenght;     // address length (byte): 4 / 16  
           char **h_addr_list;  // address list
       };  
    ********************************************************************/
    // hostent (host entry) details. 
    struct hostent *pheDetails;

    if (pthread_mutex_lock(&mutexKernelLock) != 0) {
        throw SocketException(SocketException::errUnknown);
    }

    // Call gethostbyname2 system-call to obtain hostent details and store them in pheDetails.
    pheDetails = gethostbyname2(pcNewHostName, (bIPv6) ? AF_INET6 : AF_INET);
    if (pheDetails == NULL) {
        pthread_mutex_unlock(&mutexKernelLock);
        switch (h_errno) {
            case HOST_NOT_FOUND: 
                throw SocketException(SocketException::errBadHostName);
            case NO_ADDRESS:     
                throw SocketException(SocketException::errNoIPAddress);
            case NO_RECOVERY:
            case TRY_AGAIN:      
                throw SocketException(SocketException::errDNSError);
            default:             
                throw SocketException(SocketException::errUnknown);
        }
    }

    iAddressType = pheDetails->h_addrtype;

    privSetHostName(pheDetails->h_name);
    privSetAddress(pheDetails->h_addr_list[0], pheDetails->h_length);

    // convert binary to decimal string. 
    // const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
    inet_ntop(iAddressType, pcAddress, pcStrAddress, INET6_ADDRSTRLEN);

    pthread_mutex_unlock(&mutexKernelLock);
}

/******************************************************************************/
/* void SetAddress(const char *pcNewAddress, bool bIPv6)                      */
/*                                                                            */
/* This method is called to set the IPAddress class contents to the address   */
/* specified, an extra parameter is used to allow specification of an IPv4 or */
/* IPv6 address.  The reverse look-up of the hostname may fail but this does  */
/* not mean that the address should not be set.  We do this first, setting    */
/* the correct address type, copying the provided IP address and generating   */
/* the string representation of the ip address.  Finally we obtain the host   */
/* name via a call to gethostbyaddr(), this function is not re-entrant and    */
/* all operations on the return value of the function call form a critical    */
/* section that must be protected by a Mutual Exclusion.  The MutEx is locked */
/* before calling gethostbyaddr() and unlocked at the end of the method.  If  */
/* gethostbyaddr() fails, then we cannot obtain the hostname, in this case we */
/* use the string representation of the ip address as a hostname, otherwise   */
/* we set the hostname from the values returned by the function call.  This   */
/* method does not fail as the set will always work.                          */
/******************************************************************************/
void IPAddress::SetAddress(const char *pcNewAddress, bool bIPv6) {
    struct hostent  *pheDetails;

    iAddressType = (bIPv6) ? AF_INET6 : AF_INET;
    privSetAddress(pcNewAddress, (bIPv6) ? sizeof(struct in6_addr) : sizeof(struct in_addr));
    inet_ntop(iAddressType, pcAddress, pcStrAddress, INET6_ADDRSTRLEN);

    if (pthread_mutex_lock(&mutexKernelLock) != 0) {
        throw SocketException(SocketException::errUnknown);
    }
    
    pheDetails = gethostbyaddr(pcNewAddress, iAddressLength, iAddressType);
    if (pheDetails == NULL) {
        privSetHostName(pcStrAddress);
    } else {
        privSetHostName(pheDetails->h_name);
    }

    pthread_mutex_unlock(&mutexKernelLock);
}

/******************************************************************************/
/* End of File: ipAddress.cpp                                                 */
/******************************************************************************/
